/**
 * lib/db.shim
 *
 * @author Xiongmao
 */
'use strict';

const lodash = require('lodash');
const Promise = require('bluebird');

const Checkit = require('./checkit.shim');
const Pagination = require('./pagination');

module.exports = function(db) {
  const ModelBase = db.Model;
  const toJSON = ModelBase.prototype.toJSON;

  // checkit.shim
  Checkit.Validator.prototype.unused = function(val, table, column) {
    const qb = db.knex(table);

    qb.where(column, '=', val);

    if (this._target.id) {
      qb.andWhere('id', '!=', this._target.id);
    }

    return qb.then(res => {
      if (res.length) {
        throw new Error(`The ${column}:${val} is already in use.`);
      }
    });
  };

  // export
  db.Pagination = Pagination;

  // PaginationMiddleWare for koa
  db.paginationMiddleWare = function({ paginationField = 'X-Pagination' } = {}) {
    return function* (next) {
      yield next;

      const body = this.body;
      if (body && (body instanceof Pagination)) {
        this.set(paginationField, body.exportHeader());

        this.body = body.items;
      }
    };
  };

  // override Model
  db.Model = ModelBase.extend({
    // validate
    initialize() {
      if (this.validations && typeof this.validate === 'function') {
        this.on('saving', this.validate, this);
      }
    },
    validate() {
      if (!this.validations) {
        return;
      }

      return Checkit(this.validations).run(this.attributes);
    },

    // safaSet
    safeSet(key, val, options) {
      if (!key) {
        return this;
      }

      let attrs = key;
      if (typeof key === 'string') {
        attrs = { [key]: val };
      } else {
        options = val;
      }

      // clean
      const Model = this.constructor;
      attrs = Model.clearAttributes(attrs);

      return this.set(attrs, options);
    },

    // items
    fetchItems(pageOptions, fetchOptions) {
      let _knex;
      const pager = new Pagination(pageOptions.page_size, pageOptions.page_num);

      return this.query(qb => {
        // cache
        _knex = qb.clone();

        // ignore groups
        const ignoreGroups = {
          columns: 1,
          order: 1,
          group: 1,
        };

        // filter columns
        qb._statements = qb._statements.filter(st => {
          return !ignoreGroups[st.grouping];
        });
      })
        .count('*')
        .then(total => {
          pager.setTotal(total);

          const pageNum = pager.page_num;

          return pageNum >= 1 && pageNum <= pager.max_page_num;
        })
        // fetch items
        .then(validated => {
          if (!validated) {
            return [];
          }

          // reset query
          this._knex = _knex;
          return this.query(qb => {
            qb.offset((pager.page_num - 1) * pager.page_size);
            qb.limit(pager.page_size);
          })
            .fetchAll(fetchOptions);
        })
        .then(items => {
          pager.setItems(items);

          return pager;
        });
    },

    // toJSON
    toJSON() {
      const result = toJSON.apply(this, arguments);

      // use unix timestamp output
      return lodash.mapValues(result, val => {
        if (val instanceof Date) {
          val = Math.floor(val.getTime() / 1000);
        }

        return val;
      });
    },
  }, {
    // static props
    DELETED: 1,
    NOT_DELETED: 0,

    // methdos
    clearAttributes(attrs) {
      if (attrs) {
        attrs = lodash.clone(attrs);

        // clean
        delete attrs.updated_at;
        delete attrs.created_at;
        delete attrs.id;
      }

      return attrs;
    },
    safeForge(attrs, options) {
      attrs = this.clearAttributes(attrs);

      return this.forge(attrs, options);
    },

    // fetchAllByIds
    fetchAllByIds(ids) {
      if (!Array.isArray(ids)) {
        ids = String(ids).split(',').map(id => {
          return parseInt(id, 10);
        })
          .filter(id => {
            return id > 0;
          });
      }

      const pager = new Pagination(ids.length || 10, 1);

      if (!ids.length) {
        return Promise.resolve(pager);
      }

      return this.query(qb => {
        qb.where('deleted', this.NOT_DELETED);
        qb.whereIn('id', ids);
      })
        .fetchAll()
        .then(items => {
          pager.setTotal(items.length);
          pager.setItems(items);

          return pager;
        });
    },
  });
  // 挂载事务执行器 executors: generator函数
  db.Model.prototype.buildTransaction = function(excute) {
    return db.transaction(excute);
  };
  return db;
};
